#include "fyp/render/RendererImpl.h"

//------------------------------------------------------------------------------
// additional header includes
#include <d3d9.h>

#include "fyp/render/Renderable.h"
#include "fyp/render/Shader.h"
#include "fyp/render/Texture.h"
#include "fyp/render/RenderedTexture.h"
#include "fyp/render/ScreenQuad.h"

//------------------------------------------------------------------------------
namespace ming
{
namespace fyp
{
namespace render
{
	//--------------------------------------------------------------------------
	TRendererImpl::TRendererImpl(IDirect3DDevice9 *device)
	{
		mDevice = device;
		mDevice->AddRef();

		InitTextureSamplerMappings();

		InitDefaultRenderStates();

		AcquireDefaultRenderTargetSettings();

		InitDefaultGlobalShaderParams();

		mScreenQuad = new TScreenQuad(this);
		
		mViewProjMtxDirty = true;
	}
	
	//--------------------------------------------------------------------------
	TRendererImpl::~TRendererImpl()
	{
		delete mScreenQuad;
		delete mDefaultViewport;
		mDefaultDepthBuf->Release();
		mDefaultRenderTarget->Release();
		mDevice->Release();
	}

	//--------------------------------------------------------------------------
	void* TRendererImpl::GetApiDevice()
	{
		return mDevice;
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::ClearColor(const TVec4 &color)
	{
		DWORD dwColor = 
			  DWORD( (color[3] * 255.0f)  ) << 24
			| DWORD( (color[0] * 255.0f)  ) << 16
			| DWORD( (color[1] * 255.0f)  ) << 8
			| DWORD( (color[2] * 255.0f)  ) << 0;

		mDevice->Clear(0, NULL, D3DCLEAR_TARGET, dwColor, 0.0f, 0);
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::ClearDepth(float depth)
	{
		mDevice->Clear(0, NULL, D3DCLEAR_ZBUFFER, 0, depth, 0);
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::ClearStencil(unsigned char stencil)
	{
		mDevice->Clear(0, NULL, D3DCLEAR_STENCIL, 0, 0.0f, (DWORD)stencil);
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::BeginRender()
	{
		mDevice->BeginScene();
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::EndRender()
	{
		mDevice->EndScene();
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::BindTexture(size_t channel, ITexture *texture)
	{
		DWORD stage = (DWORD)channel;

		if(texture)
		{

			ITexture::TAddressModeSettings addressModes = texture->GetAddressMode();
			ITexture::TFilterModeSettings filterModes = texture->GetFilterMode();

			mDevice->SetTexture( stage, static_cast<IDirect3DTexture9*>( texture->GetApiTexture() ) );

			mDevice->SetSamplerState( stage, D3DSAMP_MINFILTER, mTexFilterModeMapping[get<0>(filterModes)] );
			mDevice->SetSamplerState( stage, D3DSAMP_MAGFILTER, mTexFilterModeMapping[get<1>(filterModes)] );
			mDevice->SetSamplerState( stage, D3DSAMP_MIPFILTER, mTexFilterModeMapping[get<2>(filterModes)] );

			mDevice->SetSamplerState( stage, D3DSAMP_ADDRESSU, mTexAddressModeMapping[get<0>(addressModes)] );
			mDevice->SetSamplerState( stage, D3DSAMP_ADDRESSV, mTexAddressModeMapping[get<1>(addressModes)] );
			mDevice->SetSamplerState( stage, D3DSAMP_ADDRESSW, mTexAddressModeMapping[get<2>(addressModes)] );

			mDevice->SetSamplerState( stage, D3DSAMP_MAXANISOTROPY, 4);
		}
		else
		{
			mDevice->SetTexture( stage, NULL );
		}
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::BindRenderTarget(
		IRenderedTexture *rtt0,
		IRenderedTexture *rtt1,
		IRenderedTexture *rtt2,
		IRenderedTexture *rtt3,
		bool changeDepthBuffer)
	{
		BindSingleRenderTarget(0, rtt0, changeDepthBuffer);
		BindSingleRenderTarget(1, rtt1, changeDepthBuffer);
		BindSingleRenderTarget(2, rtt2, changeDepthBuffer);
		BindSingleRenderTarget(3, rtt3, changeDepthBuffer);
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::RestoreDefaultRenderTarget()
	{
		mDevice->SetRenderTarget(0, mDefaultRenderTarget);
		mDevice->SetRenderTarget(1, NULL);
		mDevice->SetRenderTarget(2, NULL);
		mDevice->SetRenderTarget(3, NULL);

		mDevice->SetDepthStencilSurface(mDefaultDepthBuf);

		mDevice->SetViewport(mDefaultViewport);
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::RegisterGlobalShaderParam(size_t id, const char* semantic)
	{
		TShaderParamRegistry::iterator iter = mShaderParamReg.find(id);
		
		if( iter == mShaderParamReg.end() )
			mShaderParamReg[id] = core::make_tuple(STR(semantic), TShaderParam() );
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::SetGlobalShaderParam(size_t id, const TShaderParam &param)
	{
		TShaderParamRegistry::iterator iter = mShaderParamReg.find(id);

		if( iter != mShaderParamReg.end() )
			get<1>(iter->second) = param;

		if(id == VIEW_MTX || id == PROJ_MTX)
			mViewProjMtxDirty = true;
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::Render(
		IRenderable *renderable,
		const TMtx44 &worldXform,
		IShader *shader)
	{
		SetGlobalShaderParam(WORLD_MTX, worldXform);

		BindGlobalShaderParams(shader);

		shader->BeginRender();

		while( shader->BeginPass() )
		{
			renderable->Render();
			shader->EndPass();
		}

		shader->EndRender();
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::RenderScreenQuad(IShader *shader)
	{
		BindGlobalShaderParams(shader);

		shader->BeginRender();

		while( shader->BeginPass() )
		{
			mScreenQuad->Render();
			shader->EndPass();
		}

		shader->EndRender();
	}

	//--------------------------------------------------------------------------
	struct BindParam
	{
		const TStr &mSemanticRef;
		IShader *mShader;

		BindParam(IShader *shader, const TStr &semantic) :
			mShader(shader),
			mSemanticRef(semantic)
		{
		}

		template<typename T>
		void operator() (const T &val)
		{
			mShader->BindParam( mSemanticRef.c_str(), &val, sizeof(T) );
		}
	};

	//--------------------------------------------------------------------------
	void TRendererImpl::InitTextureSamplerMappings()
	{
		mTexFilterModeMapping[ITexture::POINT] = D3DTEXF_POINT;
		mTexFilterModeMapping[ITexture::LINEAR] = D3DTEXF_LINEAR;
		mTexFilterModeMapping[ITexture::ANISOTROPY] = D3DTEXF_ANISOTROPIC;

		mTexAddressModeMapping[ITexture::CLAMP] = D3DTADDRESS_CLAMP;
		mTexAddressModeMapping[ITexture::WRAP] = D3DTADDRESS_WRAP;
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::InitDefaultRenderStates()
	{
		// post pixel
		mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		mDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
		mDevice->SetRenderState(D3DRS_FOGENABLE, FALSE);

		// fixed pipeline lighting
		mDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

		// z-test
		mDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
		mDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);

		// tri-mode
		mDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
		mDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	}
	
	//--------------------------------------------------------------------------
	void TRendererImpl::AcquireDefaultRenderTargetSettings()
	{
		mDevice->GetRenderTarget(0, &mDefaultRenderTarget);
		mDevice->GetDepthStencilSurface(&mDefaultDepthBuf);

		mDefaultViewport = new D3DVIEWPORT9;
		mDevice->GetViewport(mDefaultViewport);
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::InitDefaultGlobalShaderParams()
	{
		// Register Default Global Shader Params
		RegisterGlobalShaderParam(WORLD_MTX, "WORLD_MTX");
		RegisterGlobalShaderParam(VIEW_MTX, "VIEW_MTX");
		RegisterGlobalShaderParam(PROJ_MTX, "PROJ_MTX");
		RegisterGlobalShaderParam(VIEWPROJ_MTX, "VIEWPROJ_MTX");

		SetGlobalShaderParam(WORLD_MTX, TMtx44());
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::BindGlobalShaderParams(IShader *shader)
	{
		if(mViewProjMtxDirty)
		{
			TMtx44 viewProj = 
				GetShaderParam<TMtx44>(VIEW_MTX) * GetShaderParam<TMtx44>(PROJ_MTX);

			SetGlobalShaderParam(VIEWPROJ_MTX, viewProj);

			mViewProjMtxDirty = false;
		}

		for(TShaderParamRegistry::iterator iter = mShaderParamReg.begin();
			iter != mShaderParamReg.end(); 
			++iter)
		{
			apply_visitor( BindParam( shader, get<0>(iter->second) ), get<1>(iter->second) );
		}

		if( shader->HasParam("WORLDVIEWPROJ_MTX") )
		{
			TMtx44 worldViewProj = 
				GetShaderParam<TMtx44>(WORLD_MTX) * GetShaderParam<TMtx44>(VIEWPROJ_MTX);

			shader->BindParam( "WORLDVIEWPROJ_MTX", worldViewProj.getData(), sizeof(TMtx44) );
		}

		shader->CommitParams();
	}

	//--------------------------------------------------------------------------
	void TRendererImpl::BindSingleRenderTarget(size_t channel, IRenderedTexture *rtt, bool changeDepthBuffer)
	{
		if(rtt)
		{
			mDevice->SetRenderTarget(
				(DWORD)channel, 
				static_cast<IDirect3DSurface9*>( rtt->GetApiRenderTarget() ) );

			if(channel == 0)
			{
				if(changeDepthBuffer)
				{
					mDevice->SetDepthStencilSurface(
						static_cast<IDirect3DSurface9*>( rtt->GetApiDepthBuffer() ) );
				}

				tuple<size_t, size_t> rttSz = rtt->GetLogicalSize();

				D3DVIEWPORT9 vp;
				vp.Width	= (DWORD)get<0>(rttSz);
				vp.Height	= (DWORD)get<1>(rttSz);
				vp.X		= 0;
				vp.Y		= 0;
				vp.MinZ		= 0.0f;
				vp.MaxZ		= 1.0f;

				mDevice->SetViewport(&vp);
			}
		}
	}

	//--------------------------------------------------------------------------
}
}
}
